package com.example.routeplansystem.service.impl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.rmi.ServerException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.example.routeplansystem.algorithm.AlgorithmFactory;
import com.example.routeplansystem.constant.ProcessState;
import com.example.routeplansystem.constant.ResponseStatus;
import com.example.routeplansystem.dao.EdgesDao;
import com.example.routeplansystem.dao.FinalSolutionDao;
import com.example.routeplansystem.dao.NodeDao;
import com.example.routeplansystem.dao.QuestionDao;
import com.example.routeplansystem.dao.QuestionNodeDao;
import com.example.routeplansystem.dao.SolutionDao;
import com.example.routeplansystem.dao.VehicleDao;
import com.example.routeplansystem.entity.bo.ResultExcelBo;
import com.example.routeplansystem.entity.bo.SolutionExcelBO;
import com.example.routeplansystem.entity.dto.ResponseEntity;
import com.example.routeplansystem.entity.dto.Route;
import com.example.routeplansystem.entity.po.FinalSolution;
import com.example.routeplansystem.entity.po.NodePojo;
import com.example.routeplansystem.entity.po.Question;
import com.example.routeplansystem.entity.po.QuestionNodePojo;
import com.example.routeplansystem.entity.po.Solution;
import com.example.routeplansystem.entity.po.VehicleMessage;
import com.example.routeplansystem.entity.vo.FinalSolutionRes;
import com.example.routeplansystem.handler.CustomCellWriteHandler;
import com.example.routeplansystem.handler.StyleExcelHandler;
import com.example.routeplansystem.service.FinalSolutionService;
import com.example.routeplansystem.utils.DistanceUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author lucong
 * @Date 2022/4/15
 * @Description
 */

@Service
@Slf4j
public class FinalSolutionServiceImp implements FinalSolutionService {

    @Autowired
    private FinalSolutionDao finalSolutionDao;

    @Autowired
    private SolutionDao solutionDao;

    @Autowired
    private VehicleDao vehicleDao;

    @Autowired
    private NodeDao nodeDao;

    @Autowired
    private QuestionDao questionDao;

    @Autowired
    private AlgorithmFactory algorithmFactory;

    @Autowired
    private EdgesDao edgesDao;

    @Autowired
    private QuestionNodeDao questionNodeDao;


    @Autowired
    private FinalSolutionServiceImp schemeService;

    public List<FinalSolutionRes> listFinalSolution(int questionId) throws ServerException {
        List<FinalSolution> finalSolutions = finalSolutionDao.findFinalSolutionByQuestionId(questionId);
        List<Integer> finalSolutionId = finalSolutions.stream()
                .map(FinalSolution::getFinalSolutionId).collect(Collectors.toList());
        if (finalSolutionId.isEmpty()) return Collections.EMPTY_LIST;
        List<Solution> solutionList = solutionDao.findSolutionByIdList(finalSolutionId);
        if (solutionList.isEmpty()) throw new ServerException("结果已失效!!!路线不存在");
        Map<Integer, List<Route>> solutionMap = new HashMap<>(finalSolutionId.size());
        List<Integer> vehicleIds = solutionList.stream().map(Solution::getVehicleId).collect(Collectors.toList());
        List<VehicleMessage> vehicleList = vehicleDao.searchVehicleByIds(vehicleIds);
        Map<Integer, VehicleMessage> vehicleMap = vehicleList.stream()
                .collect(Collectors.toMap(VehicleMessage::getVehicleId, v -> v));
        for (Solution solution : solutionList) {
            solutionMap.putIfAbsent(solution.getFinalSolutionId(), new ArrayList<>());
            VehicleMessage vehicle = vehicleMap.get(solution.getVehicleId());
            if (vehicle == null) vehicle = new VehicleMessage(solution.getVehicleId());
            Route route = new Route(solution, vehicle);
            solutionMap.get(solution.getFinalSolutionId()).add(route);
        }
        List<FinalSolutionRes> res = finalSolutions.stream().map(fs -> {
            FinalSolutionRes fsRes = new FinalSolutionRes();
            BeanUtils.copyProperties(fs, fsRes);
            fsRes.setRoutes(solutionMap.get(fs.getFinalSolutionId()));
            fsRes.setAlgorithm(algorithmFactory.getAlgorithmName(fs.getVersion()));
            return fsRes;
        }).collect(Collectors.toList());
        return res;
    }

    public FinalSolution insertFinalSolution(int key, int questionId) {
        FinalSolution fs = new FinalSolution();
        fs.setQuestionId(questionId);
        fs.setVersion(key);
        finalSolutionDao.insertFinalSolution(fs);
        return fs;
    }

    @Transactional
    public Integer addSolution(int key, int questionId, FinalSolution fs) throws ServerException {
        List<VehicleMessage> vehicles = vehicleDao.searchVehicleByQuestionId(questionId);
        if (vehicles.isEmpty()) throw new ServerException("没有车辆可运输!");
        List<QuestionNodePojo> questionNode = questionNodeDao.selectNodesExcludeCenter(questionId);
        if (questionNode.isEmpty()) throw new ServerException("该问题下没有节点");
        List<QuestionNodePojo> centerQuestionNode = questionNodeDao.selectCenterNode(questionId);
        if (centerQuestionNode.isEmpty()) throw new ServerException("没有中心点!");
        List<Integer> nodeIds = questionNode.stream().map(QuestionNodePojo::getNodeId).collect(Collectors.toList());
        List<Integer> centerNodeIds = centerQuestionNode.stream().map(QuestionNodePojo::getNodeId).collect(Collectors.toList());
        nodeIds.addAll(centerNodeIds);
        Map<Integer, NodePojo> nodeMap = nodeDao.selectNodeByIdList(nodeIds).stream().collect(Collectors.toMap(NodePojo::getNodeId, n -> n));
        List<NodePojo> nodes = questionNode.stream().map(n -> nodeMap.get(n.getNodeId())).collect(Collectors.toList());
        List<NodePojo> centerNodes = centerQuestionNode.stream().map(n -> nodeMap.get(n.getNodeId())).collect(Collectors.toList());


        List<Solution> solutions = algorithmFactory.executeAlgorithm(nodes, centerNodes, vehicles, fs, questionId);
        for (Solution solution : solutions) {
            solutionDao.insertSolution(solution);
        }
        questionDao.updateQuestionProcessState(ProcessState.COMPLETE_DISTANCE_PREPARE, questionId);
        return fs.getFinalSolutionId();
    }

    public Integer importFinalSolution(int key, Integer userId, Integer questionId, List<SolutionExcelBO> list) {
        Integer finalSolutionId = insertFinalSolution(key, questionId).getFinalSolutionId();
        TreeMap<Integer, List<SolutionExcelBO>> map = list.stream().collect(Collectors.groupingBy(SolutionExcelBO::getRouteNo, TreeMap::new, Collectors.toList()));
        int index = 0;
        List<Solution> res = new ArrayList<>();
        for (Map.Entry<Integer, List<SolutionExcelBO>> entry : map.entrySet()) {
            List<SolutionExcelBO> value = entry.getValue();
            Collections.sort(value, Comparator.comparingInt(SolutionExcelBO::getSequence));
            List<NodePojo> routeList = value.stream().map(s -> s.toPojo(userId)).collect(Collectors.toList());
            String route = JSON.toJSONString(routeList);
            Solution solution = new Solution();
            solution.setRoute(route);
            solution.setVehicleId(index++);
            solution.setFinalSolutionId(finalSolutionId);
            res.add(solution);
        }
        for (Solution solution : res) {
            solutionDao.insertSolution(solution);
        }
        questionDao.updateQuestionProcessState(ProcessState.COMPLETE_DISTANCE_PREPARE, questionId);
        return finalSolutionId;
    }

    public FinalSolutionRes getFinalSolution(Integer finalSolutionId) throws ServerException {
        FinalSolution fs = finalSolutionDao.findFinalSolutionByFinalSolutionId(finalSolutionId);
        if (fs == null) throw new ServerException("方案不存在");
        List<Solution> solutionList = solutionDao.findSolutions(finalSolutionId);
        List<Integer> vehicleIds = solutionList.stream().map(Solution::getVehicleId).collect(Collectors.toList());
        List<VehicleMessage> vehicleList = vehicleDao.searchVehicleByIds(vehicleIds);
        Map<Integer, VehicleMessage> vehicleMap = vehicleList.stream()
                .collect(Collectors.toMap(VehicleMessage::getVehicleId, v -> v));
        ArrayList<Route> routes = new ArrayList<>();
        int i = 0;
        for (Solution solution : solutionList) {
            VehicleMessage vehicle = vehicleMap.get(solution.getVehicleId());
            if (vehicle == null) vehicle = new VehicleMessage(solution.getVehicleId());
            Route route = new Route(solution, vehicle);
            routes.add(route);
        }
        FinalSolutionRes fsRes = new FinalSolutionRes();
        BeanUtils.copyProperties(fs, fsRes);
        fsRes.setRoutes(routes);
        fsRes.setAlgorithm(algorithmFactory.getAlgorithmName(fs.getVersion()));
        return fsRes;
    }

    public List<NodePojo> getNodes(Integer finalSolutionId) throws ServerException {
        FinalSolution fs = finalSolutionDao.findFinalSolutionByFinalSolutionId(finalSolutionId);
        if (fs == null) throw new ServerException("方案不存在");
        List<Solution> solutionList = solutionDao.findSolutions(finalSolutionId);
        List<NodePojo> nodePojos = solutionList.stream().map(solution -> {
            Route route = new Route(solution, null);
            return route;
        }).flatMap(r -> r.getRoute().stream()).collect(Collectors.toList());
        return nodePojos;
    }

    @Override
    public void removeAllFinalSolutionByQuestionId(int questionId) {
        //修改算法执行状态：
        questionDao.updateSimpleExecuted(questionId, 0);
        questionDao.updateGeneticExecuted(questionId, 0);

        //查询当前问题Id的所有解决方案，并返回了解决方案ID列表：
        List<Integer> finalSolutionIds = finalSolutionDao.findAllFinalSolutionId(questionId);
        //遍历列表：
        finalSolutionIds.forEach(this::removeFinalSolution);
    }

    public void removeFinalSolution(int finalSolutionId) {
        finalSolutionDao.deleteFinalSolution(finalSolutionId);
        solutionDao.deleteSolutionByFinalSolutionId(finalSolutionId);
    }


    /**
     * 将该方案的路线以Excel的形式导出
     *
     * @param finalSolutionId
     * @return
     */
    @Override
    public ResponseEntity<String> resultExport(HttpServletRequest request, HttpServletResponse response, int finalSolutionId) {
        //先判断该finalSolutionId对应的方案是否能正常使用，不能就不用执行后面的了
        FinalSolutionRes res = null;
        try {
            res = schemeService.getFinalSolution(finalSolutionId);
        } catch (ServerException e) {
            return new ResponseEntity<>(ResponseStatus.FILE_TRANSFER_FIAL, e.getMessage(), null);
        }


        //用来判断是否是第一次往Excel表里写数据，后面写数据不需要表头
        boolean flag = true;
        //导出Excel的名字
        Question question = questionDao.selectQuestionByFinalSolutionID(finalSolutionId);
        String fileName = question.getQuestionName() + "项目" + "的路线";

        //将response置空
        response.reset();
        // 设置response的Header
        response.setCharacterEncoding("UTF-8");
        //Content-Disposition的作用：告知浏览器以何种方式显示响应返回的文件，用浏览器打开还是以附件的形式下载到本地保存
        //attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
        // filename表示文件的默认名称，因为网络传输只支持URL编码的相关支付，因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
        try {
            // response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            //设置内容类型（后面发现写这个，会有一个报错（虽然没有影响），但让程序自己判断，也是对的）
            //   response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//            response.setContentType("application/vnd.ms-excel");
            //设置标题
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "utf-8"));
        } catch (UnsupportedEncodingException e) {
            log.error("设置标题失败" + e.getMessage(), e);
        }


        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ResultExcelBo.class).build()) {

            List<Route> routes = res.getRoutes();

            //计算每条路线点的数量，因为路线是从中心点出发，所以每条路线首尾有两个中心点
            int num = (routes.get(0).getRoute().size());

            // 这里注意 如果同一个sheet只要创建一次
            WriteSheet writeSheet = EasyExcel.writerSheet("路线")
                    .registerWriteHandler(new StyleExcelHandler()).registerWriteHandler(new CustomCellWriteHandler()).build();

            int vehicleIndex = 0; //这是第几辆车
            for (Route route : routes) {
                vehicleIndex++;
                //这个变量记录这个车辆送的是它路线上的第几个点,每次从0开始
                int i = 0;
                //用来记录车辆从起点开始的路程，通过查edges表得出
                int totalDistance = 0;
                List<NodePojo> nodePojoList = route.getRoute();
                VehicleMessage vehicle = route.getVehicle();
                if (vehicle.getVehicleNumber() == null) {
                    vehicle.setVehicleNumber("车辆" + vehicleIndex);
                }
                List<ResultExcelBo> resultList = new ArrayList<>();

                Integer startId = nodePojoList.get(0).getNodeId();
                NodePojo startNode = nodePojoList.get(0);
                for (NodePojo nodePojo : nodePojoList) {
                    ResultExcelBo resultExcelBo = new ResultExcelBo(nodePojo, vehicle);
                    resultExcelBo.setOrder(i++);
                    Integer endId = nodePojo.getNodeId();
                    Integer disInt;
                    if (startId == null || endId == null) {
                        // 没有nodeId,查询地图api
                        disInt = DistanceUtil.getDistance(startNode.getLng(), startNode.getLat(), nodePojo.getLng(), nodePojo.getLat());
                    } else {
                        // 有nodeId,从距离矩阵中取值
                        disInt = edgesDao.selectDisInt(startId, endId);
                        if (disInt == null) disInt = DistanceUtil.getDistance(startNode, nodePojo);
                    }
                    totalDistance += disInt == null ? 0 : disInt;
                    resultExcelBo.setTotalDistance(totalDistance);
                    resultList.add(resultExcelBo);
                    startId = endId;
                    startNode = nodePojo;
                }
                excelWriter.write(resultList, writeSheet);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public ResponseEntity<Integer> getFinalSolutionStatus(int finalSolutionId) {
        Integer status = finalSolutionDao.findStatusByFinalSolutionId(finalSolutionId);
        if (status == null)
            return new ResponseEntity<Integer>(ResponseStatus.FAILURE_RESPONSE, "未能查找到该方案的状态，请确认是否正确", status);
        return new ResponseEntity<Integer>(ResponseStatus.SUCCESS_RESPONSE, "成功", status);
    }

}
